#pragma once

#include "types.h"
#include <array>
#include <cstdint>
#include <cstring>
#include <ctime>
#include <list>
#include <memory>
#include <time.h>
#include <vector>

namespace Trade {

const uint8_t DELTA_ARRAY_LENGTH = 15;
using PriceDelta = std::array<float, DELTA_ARRAY_LENGTH>;

enum class DIR_CHANGED_TYPE : uint8_t
{
  UNKOWN = 0,
  NO_CHANGE = 1,
  POSSIBLE = 2,
  CHANGE_DIR = 3,
};

struct SpotDirAndTime
{
  // 1. if the ctype is CHANGE_DIR, (or maybe POSSIBLE)
  //    AFTER-the-TIMER-out, the dir will change-to-dir
  //    and keep the new dir duo to durantion-timer
  // 2. if the ctype is NO_CHANGE
  //    AFTER-the-TIMER-out, the change will be possible
  DIR_CHANGED_TYPE ctype = DIR_CHANGED_TYPE::UNKOWN;
  dir_t cur_dir = 0;
  int8_t cur_idx = 0;
  dir_t possible_dir = 0;
  dir_t change_to_dir = 0;
  time_t after_timer = 0;
  time_t durantion_timer = 0;

  void reset() noexcept { memset((void*)(this), 0, sizeof(SpotDirAndTime)); }
  void print(const char* title, const time_t t_now) const noexcept;
};

using TimerOutVector = std::vector<int8_t>;
using TimerArray = time_t[3];

struct TimerSpot
{
  dir_t d;
  TimerArray t;

  time_t get_time(const dir_t dir, const time_t t_now) const noexcept
  {
    time_t tm = 0;
    if (d != -dir) {
      if (t[0] > t_now)
        tm = t[0];
      else if (t[1] > t_now)
        tm = t[1];
      else if (t[2] > t_now)
        tm = t[2];
    }
    return tm;
  }

  int8_t avaliable_index(const time_t t_now) const noexcept
  {
    int8_t idx = -1;
    if (t[0] - t_now > 120)
      idx = 0;
    else if (t[1] - t_now > 120)
      idx = 1;
    else if (t[2] - t_now > 120)
      idx = 2;
    return idx;
  }

  static bool is_avaliable(const int8_t idx) noexcept { return (idx >= 0); }
};

struct StageDirs
{
  dir_t mid_dir = 0; // midlle direction
  dir_t ein_dir = 0; // ein = end, far stage direcion
  dir_t big_dir = 0; // first - spot
  dir_t sdg_dir = 0;
  int8_t n_t1 = 0;
  int8_t n_t2 = 0;
  bool gb_no_mid = false;
  bool gb_no_ein = false;
  time_t tm = 0;
  TimerSpot ts_mid_l; // mid - left-timer
  TimerSpot ts_mid_r; // mid - right-timer
  TimerSpot ts_ein_l; // ein - left-timer
  TimerSpot ts_ein_r; // ein - right-timer
  // -------o-------------o------> time
  //     (ts_xx_l)   (ts_xx_r)

  void reset() noexcept { memset((void*)(this), 0, sizeof(StageDirs)); }

  void print(const time_t t, const dir_t w, const int64_t idx) const noexcept;
  void check_zero_dir() noexcept;

  bool is_3same_dir_(const dir_t d) const noexcept;
  bool is_2same_dir_(const dir_t d) const noexcept;
  bool is_opposite_dir_(const dir_t d) const noexcept;
  bool have_zero_dir_() const noexcept;
  bool both_zero_dir_() const noexcept;

  time_t timer_to_mid_dir(const dir_t d) const noexcept;
  time_t timer_to_ein_dir(const dir_t d) const noexcept;
  dir_t dir_to_mid_next(const dir_t d) const noexcept;
  dir_t dir_to_ein_next(const dir_t d) const noexcept;
  dir_t dirs_mid(time_t& tm, const dir_t d = 0) const noexcept;
  dir_t dirs_next_mid(time_t& tm, const dir_t d = 0) const noexcept;
  dir_t dirs_ein(time_t& tm, const dir_t d = 0) const noexcept;
  dir_t dirs_next_ein(time_t& tm, const dir_t d = 0) const noexcept;

  // int8_t retrive_stage_dir_timer(const dir_t d,
  //                                const time_t t_now,
  //                                TimeAfterTime& tat) noexcept;

  // is_mid for mid, else for ein
  DIR_CHANGED_TYPE is_possible_to_change__(SpotDirAndTime& pdc,
                                           const time_t t_now,
                                           const bool is_mid) noexcept;

  bool is_possible_to_change(SpotDirAndTime& pdc_mid,
                             SpotDirAndTime& pdc_ein,
                             const time_t t_now) noexcept;
};

struct PossibleDirChange
{
  SpotDirAndTime mid;
  SpotDirAndTime ein;

  void print(const time_t t_now) const noexcept;
  uint8_t is_going_to_dir(const dir_t d, const time_t t_now) const noexcept;
  uint8_t possiblely_goto_dir(const dir_t d, const time_t t_now) const noexcept;
  dir_t guess_the_dir(const dir_t d, const time_t t_now) const noexcept;
  time_t get_minimal_time_to_cur_dir(const dir_t d,
                                     const time_t t_now) const noexcept;
  bool one_possible_to_dir(const dir_t d) const noexcept;
  uint8_t confused() const noexcept;
  uint8_t time_is_very_short(const time_t t_now) const noexcept;
  dir_t current_dir() const noexcept;
  dir_t possible_dir() const noexcept;
  bool no_mid_ein() const noexcept;
};

struct DirMarker
{
  dir_t dir = 0;
  time_t tm = 0;
  price_t hpx = 0;
  DirMarker(const dir_t d, const time_t t, const price_t px) noexcept
    : dir(d)
    , tm(t)
    , hpx(px)
  {
  }
  int durantion(const time_t ts) const noexcept { return int(ts - tm) / 60; }
};

using DirMarkerPtr = std::shared_ptr<DirMarker>;
using DirMarkerList = std::list<DirMarkerPtr>;

enum DIR_DREAMER_TYPE : uint8_t
{
  DD_UNKNOW = 0,
  DD_UP = 1,
  DD_DN = 2,
  DD_UP_ZERO = 3,
  DD_DN_ZERO = 4,
  DD_UP_SWING = 5,
  DD_DN_SWING = 6,
  DD_SWING = 7,
};


extern dir_t
ddt_to_dir(const DIR_DREAMER_TYPE ddt) noexcept;
extern DIR_DREAMER_TYPE
dir_to_ddt(const dir_t d) noexcept;

struct DirList3
{
  dir_t d1 = 0;
  dir_t d2 = 0;
  time_t t1 = 0;
  time_t t2 = 0;
  price_t hp1 = 0;
  price_t hp2 = 0;
  dir_t long_dir = 0;
  int long_dur = 0;
  time_t long_tm = 0;
  price_t long_hpx = 0;

  dir_t get_first_dir() const noexcept;
  dir_t get_second_dir() const noexcept;
  time_t get_first_time() const noexcept;
  time_t get_second_time() const noexcept;
  price_t get_first_hp() const noexcept;
  price_t get_second_hp() const noexcept;
  int get_first_durantion(const time_t ts) const noexcept;
  int get_second_durantion() const noexcept;

  void print(const char* name,
             const dir_t d,
             const time_t t_now,
             const price_t hp_now) const noexcept;
  void print(const char* name,
             const time_t t_now,
             const price_t hp_now) const noexcept;
  bool is_swing_dir(const time_t t_now) const noexcept;
  DIR_DREAMER_TYPE retrive_dir_type(const dir_t cur_dir,
                                    const time_t ts) const noexcept;
  void print_with_hp(const char* name,
                     const time_t t_now,
                     const price_t hp_now) const noexcept;

  void set_d1(const dir_t d, const time_t t, const price_t hp) noexcept;
  void set_d2(const dir_t d, const time_t t, const price_t hp) noexcept;
  void set_long(const dir_t d, const time_t t, const price_t hp) noexcept;
  dir_t possible_dir(const time_t t_now) const noexcept;
  DIR_DREAMER_TYPE possible_dir_type(const time_t t_now) const noexcept;

  bool operator!=(const DirList3& dl) const
  {
    if (d1 != dl.d1)
      return true;
    if (d2 != dl.d2)
      return true;
    if (t1 != dl.t1)
      return true;
    if (t2 != dl.t2)
      return true;
    if (hp1 != dl.hp1)
      return true;
    if (hp2 != dl.hp2)
      return true;
    if (long_dir != dl.long_dir)
      return true;
    if (long_dur != dl.long_dur)
      return true;
    if (long_tm != dl.long_tm)
      return true;
    if (long_hpx != dl.long_hpx)
      return true;

    return false;
  }
};

struct Dir5
{
  dir_t wave = 0;
  dir_t ext = 0;
  dir_t mid = 0;
  dir_t ein = 0;
  dir_t g2 = 0;

  bool reversed(const Dir5& d5) noexcept
  {
    return (wave = -d5.wave && ext == -d5.ext && mid == -d5.mid &&
                   ein == -d5.ein && g2 == -d5.g2);
  }
};

struct DirList
{
  uint16_t _deep = 1000;
  dir_t long_dir = 0;
  int long_dur = 0;
  time_t long_tm = 0;
  price_t long_hpx = 0;

  DirMarkerList dm_list;

  virtual ~DirList() { dm_list.clear(); }

  bool new_dir_maker(const dir_t d,
                     const time_t tm,
                     const price_t hpx) noexcept;
  dir_t get_first_dir() const noexcept;
  DirMarkerPtr get_first() const noexcept;
  DirMarkerPtr get_first_opposite() const noexcept;
  DirMarkerPtr get_first_same_side() const noexcept;
  DirMarkerPtr get_second_opposite() const noexcept;
  DirMarkerPtr get_second_same_side() const noexcept;
  bool is_swing_dir(const time_t t_now) const noexcept;
  bool first_time_is_good(const time_t t_now) const noexcept;
  bool first_pdc_time_is_good(const time_t t_now) const noexcept;
  dir_t get_second_dir() const noexcept;
  int get_first_durantion(const time_t t_now) const noexcept;
  int get_second_durantion() const noexcept;
  void set_deep(const uint16_t deep) noexcept { _deep = deep; }
  int durantion(const dir_t d, const int64_t t_now) noexcept;
  void print(const char* name, const time_t t_now) const noexcept;
  void print_with_hp(const char* name,
                     const time_t t_now,
                     const price_t hp) const noexcept;
  void print(const time_t t_now, const int64_t idx) const noexcept;
  void print_four(const time_t t_now, const price_t hp_now) const noexcept;
  void print(const char* name [[maybe_unused]],
             const dir_t d [[maybe_unused]],
             const time_t t_now [[maybe_unused]]) const noexcept
  {
  }
  dir_t guess_the_big_dir(const time_t t_now,
                          const price_t hp_now,
                          bool& its_nature) noexcept;
  dir_t guess_the_ce_dir(bool& its_nature) const noexcept;
  price_t get_hp_delta() const noexcept;
  bool long_time_is(const dir_t d, const time_t ts) const noexcept;
  bool long_long_time(const dir_t d, const time_t ts) const noexcept;
  time_t get_first_time() const noexcept;
  time_t get_second_time() const noexcept;
  time_t get_long_time() const noexcept;
  time_t get_aux_time(const time_t ts, dir_t& d, price_t& hp) const noexcept;

  void conveter_to_3(DirList3& d3) const noexcept;

  DIR_DREAMER_TYPE retrive_dir_type(const dir_t cur_dir,
                                    const time_t ts) const noexcept;
  price_t get_first_hp() const noexcept { return (0); }
  price_t get_second_hp() const noexcept { return (0); }
};


struct NearFarTrend
{
  dir_t d_near = 0;
  dir_t d_far = 0;
  time_t t1 = 0;
  time_t t2 = 0;

  time_t get_solved_near_time() const noexcept;
  dir_t get_solved_near_dir() const noexcept;
  time_t get_solved_far_time() const noexcept;
  dir_t get_sovled_far_dir() const noexcept;
  void get_solved_dir_time(dir_t& d_near,
                           dir_t& d_far,
                           time_t& t1,
                           time_t& t2) const noexcept;
};

extern event_t
near_far_trend_chagned(const NearFarTrend& nft) noexcept;


} // namespace Trade
